#include <iostream>
#include <string>
#include <mutex>
#include <set>
#include <thread>
#include <condition_variable>
using namespace std;
mutex sCoutLock;
mutex sTotalLock;
class Account
{
public:
    Account(string name, double money)
        :m_name(name), m_money(money){}
public:
    void changeMoney(double amount)
    {

        unique_lock<mutex> u_lock(m_metex);
        m_condition.wait(u_lock, [this, amount]{
            return m_money + amount > 0;
        });
        m_money += amount;
        m_condition.notify_all();

        lock_guard<mutex> lock(sCoutLock);
        cout << "id:" << this_thread::get_id()
             << " name:" << m_name
             << " money:" << m_money << endl;
        /**/
    }
    const string& getName()
    {
        return m_name;
    }
    const double& getMoney()
    {
        return m_money;
    }
    mutex* getLock()
    {
        return &m_metex;
    }
private:
    string m_name;
    double m_money;
    mutex m_metex;
    condition_variable m_condition;
};

class Bank
{
public:
    Bank() = default;
public:
    void addCount(Account* pAccount)
    {
        if (pAccount != nullptr)
        {
            m_accounts.insert(pAccount);
        }
    }
    bool transferMoney(Account* pAccountA, Account* pAccountB, double amount)
    {
        if (pAccountA == nullptr || pAccountB == nullptr)
        {
            return false;
        }
        /*
        lock(*pAccountA->getLock(), *pAccountB->getLock());
        lock_guard<mutex> guildA(*pAccountA->getLock(), std::adopt_lock);
        lock_guard<mutex> guildB(*pAccountB->getLock(), std::adopt_lock);
        if (pAccountA->getMoney() < amount)
        {
            return false;
        }
        */
        pAccountA->changeMoney(-amount);
        pAccountB->changeMoney(amount);

        return true;
    }
    double totalMoney() const
    {
        double sum = 0;
        for (auto a : m_accounts)
        {
            //unique_lock<mutex> lock(*a->getLock());
            sum += a->getMoney();
        }
        return sum;
    }
private:
    set<Account*> m_accounts;
};

void randomTransfer(Bank* pBank, Account* pAccountA, Account* pAccountB)
{
    while (true) {
        double randomMoney = ((double)rand() / RAND_MAX) * 10;
        if (pBank->transferMoney(pAccountA, pAccountB, randomMoney))
        {
            lock_guard<mutex> lock(sCoutLock);
            cout << "Transfer " << randomMoney << " from " << pAccountA->getName()
                 << "(" << pAccountA->getMoney() << ")"
                 << " to " << pAccountB->getName() << "(" << pAccountB->getMoney() << ")"
                 << ", Bank totalMoney:" << pBank->totalMoney() << endl;
            //sCoutLock.unlock();
        }
        else
        {
            sCoutLock.lock();
            cout << "Transfer failed, "
                 << pAccountA->getName() << " has only $" << pAccountA->getMoney() << ", but "
                 << randomMoney << " required" << endl;
            sCoutLock.unlock();
        }
    }
}

void addMoney(Account* pAccount)
{
    while (true) {
        pAccount->changeMoney(10);
        this_thread::sleep_for(chrono::seconds(5));
    }
}

int main()
{
    cout << "Hello World!" << endl;
    Account a("jack", 100);
    Account b("linch", 100);

    Bank bank;
    bank.addCount(&a);
    bank.addCount(&b);

    thread t1(randomTransfer, &bank, &a, &b);
    thread t2(randomTransfer, &bank, &a, &b);
    thread t3(addMoney, &a);

    t1.join();
    t2.join();
    t3.join();

    return 0;
}
